use std::io::{Read, Seek, Write};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("IO Error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Nul error: {0}")]
    WideStringNul(#[from] widestring::error::NulError<u16>),
    #[error("ContainsNul error: {0}")]
    ContainsNul(#[from] widestring::error::ContainsNul<u16>),
    #[error("Utf16 conversion error: {0}")]
    Utf16(#[from] widestring::error::Utf16Error),
    #[error("FromVecWithNul conversion error: {0}")]
    FromVecWithNul(#[from] std::ffi::FromVecWithNulError),
    #[error("IntoString conversion error: {0}")]
    IntoString(#[from] std::ffi::IntoStringError),
    #[error("Nul error: {0}")]
    FfiNul(#[from] std::ffi::NulError),
}

pub trait Reader {
    fn read_wide_string(&mut self) -> Result<String, Error>;
    fn read_string(&mut self) -> Result<String, Error>;
    fn read_u8(&mut self) -> Result<u8, Error>;
    fn read_u16_le(&mut self) -> Result<u16, Error>;
    fn read_u32_le(&mut self) -> Result<u32, Error>;
    fn read_u64_le(&mut self) -> Result<u64, Error>;
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>;
}

pub trait Writer {
    fn write_wide_string<T: AsRef<str>>(&mut self, value: T) -> Result<usize, Error>;
    fn write_string<T: AsRef<str>>(&mut self, value: T) -> Result<usize, Error>;
    fn write_u8(&mut self, value: u8) -> Result<usize, Error>;
    fn write_u16_le(&mut self, value: u16) -> Result<usize, Error>;
    fn write_u32_le(&mut self, value: u32) -> Result<usize, Error>;
    fn write_u64_le(&mut self, value: u64) -> Result<usize, Error>;
    fn write_all(&mut self, buf: &[u8]) -> Result<usize, Error>;
}

pub struct Cursor<T> {
    inner: T,
}

impl<T> Cursor<T> {
    pub fn new(inner: T) -> Self {
        Self { inner }
    }
}

impl<T: Read> Reader for Cursor<T> {
    fn read_wide_string(&mut self) -> Result<String, Error> {
        let mut output: Vec<u16> = Vec::with_capacity(1024);
        let mut character = self.read_u16_le()?;
        while character != 0 {
            output.push(character);
            character = self.read_u16_le()?;
        }
        // Push last null
        output.push(character);
        Ok(widestring::U16CStr::from_slice(&output)?.to_string()?)
    }

    fn read_string(&mut self) -> Result<String, Error> {
        let mut output: Vec<u8> = Vec::with_capacity(1024);
        let mut character = self.read_u8()?;
        while character != 0 {
            output.push(character);
            character = self.read_u8()?;
        }
        // Push last null
        output.push(character);
        Ok(std::ffi::CString::from_vec_with_nul(output)?.into_string()?)
    }

    fn read_u8(&mut self) -> Result<u8, Error> {
        let mut result = [0u8; 1];
        self.inner.read_exact(&mut result)?;
        Ok(u8::from_le_bytes(result))
    }

    fn read_u16_le(&mut self) -> Result<u16, Error> {
        let mut result = [0u8; 2];
        self.inner.read_exact(&mut result)?;
        Ok(u16::from_le_bytes(result))
    }

    fn read_u32_le(&mut self) -> Result<u32, Error> {
        let mut result = [0u8; 4];
        self.inner.read_exact(&mut result)?;
        Ok(u32::from_le_bytes(result))
    }

    fn read_u64_le(&mut self) -> Result<u64, Error> {
        let mut result = [0u8; 8];
        self.inner.read_exact(&mut result)?;
        Ok(u64::from_le_bytes(result))
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> {
        Ok(self.inner.read_exact(buf)?)
    }
}

impl<T: Write + Seek> Writer for Cursor<T> {
    fn write_wide_string<S: AsRef<str>>(&mut self, value: S) -> Result<usize, Error> {
        let wide_string = widestring::U16CString::from_str(value.as_ref())?;
        let data = wide_string.into_vec_with_nul();
        let mut output = Vec::with_capacity(data.len() * 2);
        for element in data {
            output.extend_from_slice(&element.to_le_bytes());
        }
        self.write_all(&output)
    }

    fn write_string<S: AsRef<str>>(&mut self, value: S) -> Result<usize, Error> {
        let string = std::ffi::CString::new(value.as_ref())?;
        self.write_all(string.as_bytes_with_nul())
    }

    fn write_u8(&mut self, value: u8) -> Result<usize, Error> {
        self.write_all(&[value])
    }

    fn write_u16_le(&mut self, value: u16) -> Result<usize, Error> {
        self.write_all(&value.to_le_bytes())
    }

    fn write_u32_le(&mut self, value: u32) -> Result<usize, Error> {
        self.write_all(&value.to_le_bytes())
    }

    fn write_u64_le(&mut self, value: u64) -> Result<usize, Error> {
        self.write_all(&value.to_le_bytes())
    }

    fn write_all(&mut self, buf: &[u8]) -> Result<usize, Error> {
        self.inner.write_all(buf)?;
        Ok(self.inner.stream_position()? as usize)
    }
}